-- SPDX-License-Identifier: GPL-3.0-or-later

local VslUtil = {}

--狂野模式开关
--记得把zzsystem.lua里的也设置为true
-- VslUtil.wild_mode = false --狂野模式！！（仅限服务器用，解放偷梁换柱、猴子）

--------------------------------------------------
--Vsl包名
--好吧这疑似没什么用，后续再考虑怎么处理改名问题
--------------------------------------------------

-- VslUtil.extensionName = "vupslash"
VslUtil.extensionName = "VslTest"

--------------------------------------------------
--combobox大全
--------------------------------------------------

VslUtil.basic = {
  "slash", "peach", "analeptic",
  "fire__slash", "thunder__slash", "v_ice__slash"
}
VslUtil.normal_tricks = {
  "dismantlement", "snatch", "duel", "collateral",
  "ex_nihilo", "savage_assault", "archery_attack", "god_salvation",
  "amazing_grace", "iron_chain", "fire_attack",
}
VslUtil.not_damage_tricks = {
  "dismantlement", "snatch", "collateral",
  "ex_nihilo", "god_salvation",
  "amazing_grace", "iron_chain",
}
VslUtil.damage_tricks = {
  "duel", "fire_attack", "savage_assault", "archery_attack",
}
VslUtil.suits = {
   "heart", "diamond", "spade", "club",
}

--------------------------------------------------
--标签显示大全
--------------------------------------------------

VslUtil.short_card_type = {
  basic = "基",
  trick = "锦",
  equip = "装",
}

VslUtil.card_suits_table = {
  spade = "♠",
  club = "♣",
  heart = "♥",
  diamond = "♦",
}

--------------------------------------------------
--三目运算符
--------------------------------------------------

VslUtil.Ternary = function(A, B, C)
	if A then
		return B
	else
		return C
	end
end

--- 更换牌所处区域的描述
---@param card_position integer
---@return integer
VslUtil.cpos_change = function(card_position)
  local car = Card.Unknown
  if card_position == Player.Hand then
    car = Card.PlayerHand
  elseif card_position == Player.Equip then
    car = Card.PlayerEquip
  elseif card_position == Player.Judge then
    car = Card.PlayerJudge
  end
  return car
end

--- 交换两个角色的同区域手牌
---@param player ServerPlayer
---@param target1 ServerPlayer
---@param target2 ServerPlayer
---@param card_position integer
---@param skillname string
VslUtil.exchange_cards = function(player, target1, target2, card_position, skillname)
  if target1:isAlive() and target2:isAlive() then
    local room = target1.room
    -- local old_cards1 = target1.player_cards[card_position]
    -- local old_cards2 = target2.player_cards[card_position]
    local cards1 = table.clone(target1.player_cards[card_position])
    local cards2 = table.clone(target2.player_cards[card_position])
    -- local cards1 = target1.player_cards[card_position]
    -- local cards2 = target2.player_cards[card_position]
    local moveInfos = {}
    local move_vis = false
    -- if card_position ~= Player.Hand then
    --   move_vis = true
    -- end
    if #cards1 > 0 then
      table.insert(moveInfos, {
        from = target1.id,
        ids = cards1,
        toArea = Card.Processing,
        moveReason = fk.ReasonExchange,
        proposer = player.id,
        skillName = skillname,
        moveVisible = move_vis
      })
    end
    if #cards2 > 0 then
      table.insert(moveInfos, {
        from = target2.id,
        ids = cards2,
        toArea = Card.Processing,
        moveReason = fk.ReasonExchange,
        proposer = player.id,
        skillName = skillname,
        moveVisible = move_vis
      })
    end
    if #moveInfos > 0 then
      room:moveCards(table.unpack(moveInfos))
    end
    moveInfos = {}
    if not target2.dead then
      local to_ex_cards1 = table.filter(cards1, function (id)
        return room:getCardArea(id) == Card.Processing
      end)
      if card_position == Player.Equip then
        to_ex_cards1 = table.filter(cards1, function (id)
          return room:getCardArea(id) == Card.Processing 
          and target2:getEquipment(Fk:getCardById(id).sub_type) == nil
          and #target2:getAvailableEquipSlots(Fk:getCardById(id).sub_type) > 0
        end)
      --判定区废弃的问题和判定区已经有一样的牌这个没判定，暂时应该不碍事···（害怕）
      end
      if #to_ex_cards1 > 0 then
        table.insert(moveInfos, {
          ids = to_ex_cards1,
          fromArea = Card.Processing,
          to = target2.id,
          toArea = VslUtil.cpos_change(card_position),
          moveReason = fk.ReasonExchange,
          proposer = player.id,
          skillName = skillname,
          moveVisible = move_vis
        })
      end
    end
    if not target1.dead then
      local to_ex_cards2 = table.filter(cards2, function (id)
        return room:getCardArea(id) == Card.Processing
      end)
      if card_position == Player.Equip then
        to_ex_cards2 = table.filter(cards2, function (id)
          return room:getCardArea(id) == Card.Processing 
          and target1:getEquipment(Fk:getCardById(id).sub_type) == nil
          and #target1:getAvailableEquipSlots(Fk:getCardById(id).sub_type) > 0
        end)
      end
      if #to_ex_cards2 > 0 then
        table.insert(moveInfos, {
          ids = to_ex_cards2,
          fromArea = Card.Processing,
          to = target1.id,
          toArea = VslUtil.cpos_change(card_position),
          moveReason = fk.ReasonExchange,
          proposer = player.id,
          skillName = skillname,
          moveVisible = move_vis
        })
      end
    end
    if #moveInfos > 0 then
      room:moveCards(table.unpack(moveInfos))
    end
    table.insertTable(cards1, cards2)
    local dis_cards = table.filter(cards1, function (id)
      return room:getCardArea(id) == Card.Processing
    end)
    if #dis_cards > 0 then
      local dummy = Fk:cloneCard("dilu")
      dummy:addSubcards(dis_cards)
      room:moveCardTo(dummy, Card.DiscardPile, nil, fk.ReasonPutIntoDiscardPile, skillname)
    end
  end
end

--- 附身式换角色状态
---@param proposer ServerPlayer
---@param player ServerPlayer
---@param player2 ServerPlayer
---@param skillname string
---@param invalid_trigger boolean
VslUtil.swap_player = function(proposer, player, player2, skillname, invalid_trigger)
  local room = player.room
  if invalid_trigger then
    for _,p in ipairs(room:getAllPlayers()) do
      room:setPlayerMark(p, "swap_invalid", 1)
    end
  end
  VslUtil.exchange_cards(proposer, player, player2, Player.Hand, skillname)
  VslUtil.exchange_cards(proposer, player, player2, Player.Equip, skillname)
  VslUtil.exchange_cards(proposer, player, player2, Player.Judge, skillname)
  if player:isAlive() and player2:isAlive() then
    room:swapSeat(player, player2)
  end
  if invalid_trigger then
    for _,p in ipairs(room:getAllPlayers()) do
      room:setPlayerMark(p, "swap_invalid", 0)
    end
  end
end

--- 返回牌堆
---@param player ServerPlayer
---@param cards integer[]
---@param skillName string
VslUtil.return_draw_pile = function(player, cards, skillName)
  local room = player.room
  local ram = math.random(1, #room.draw_pile + 1)
  room:moveCards({
      ids = cards,
      from = player.id,
      toArea = Card.DrawPile,
      moveReason = fk.ReasonJustMove,
      skillName = skillName,
      drawPilePosition = ram,
  })
end

--- 除外牌堆字符串转化
---@param reason string
VslUtil.Banish_Exchange = function(reason)
  local _,pis = reason:find("-pile")
  local pie,_ = reason:find("*")
  local true_reason = reason
  if pis and pie and pis < pie then
    true_reason = reason:sub(1, pis-5)
  end
  return true_reason
end

--- 除外N张牌(输入牌量和hej版)
---@param from ServerPlayer
---@param to ServerPlayer
---@param N integer
---@param flag string
---@param reason string
---@param pile_show boolean
VslUtil.BanishNCards = function(from, to, N, flag, reason, pile_show)
  N = N or 1
  flag = flag or "he"
  pile_show = pile_show or false
  local room = from.room
  local ids = {}
  local places = {}
  if to:getMark(reason.."_ids") ~= 0 then
    ids = to:getMark(reason.."_ids"):split("+")
  end
  if to:getMark(reason.."_places") ~= 0 then
    places = to:getMark(reason.."_places"):split("+")
  end
  local dummy = Fk:cloneCard("dilu")
  local true_reason = VslUtil.Banish_Exchange(reason)
  local card_ids = room:askForCardsChosen(from, to, 1, N, flag, true_reason)
  for _,id in ipairs(card_ids) do
    table.insert(ids, id)
    table.insert(places, room:getCardArea(id))
    dummy:addSubcard(id)
  end
  if #dummy.subcards > 0 then
    room:sendLog{
      type = "#v_banish_log",
      from = from.id,
      --log.to是数组
      to = {to.id},
      arg = true_reason,
      arg2 = #dummy.subcards,
    }
    to:addToPile(reason, dummy, pile_show, true_reason)
  end
  if reason ~= true_reason then
    room:setPlayerMark(to, reason.."_ids", table.concat(ids, "+"))
    room:setPlayerMark(to, reason.."_places", table.concat(places, "+"))
  end
end

--- 除外N张牌(输入card_ids版本)
---@param from ServerPlayer
---@param to ServerPlayer
---@param card_ids integer[]
---@param reason string
---@param pile_show boolean
VslUtil.BanishCards_Table = function(from, to, card_ids, reason, pile_show)
  pile_show = pile_show or false
  local room = from.room
  local ids = {}
  local places = {}
  if to:getMark(reason.."_ids") ~= 0 then
    ids = to:getMark(reason.."_ids"):split("+")
  end
  if to:getMark(reason.."_places") ~= 0 then
    places = to:getMark(reason.."_places"):split("+")
  end
  local dummy = Fk:cloneCard("dilu")
  local true_reason = VslUtil.Banish_Exchange(reason)
  for _,id in ipairs(card_ids) do
    table.insert(ids, id)
    table.insert(places, room:getCardArea(id))
    dummy:addSubcard(id)
  end
  if #dummy.subcards > 0 then
    room:sendLog{
      type = "#v_banish_log",
      from = from.id,
      --log.to是数组
      to = {to.id},
      arg = true_reason,
      arg2 = #dummy.subcards,
    }
    to:addToPile(reason, dummy, pile_show, true_reason)
  end
  if reason ~= true_reason then
    room:setPlayerMark(to, reason.."_ids", table.concat(ids, "+"))
    room:setPlayerMark(to, reason.."_places", table.concat(places, "+"))
  end
end

--- 除外N张牌(输入dummy版本)
---@param from ServerPlayer
---@param to ServerPlayer
---@param dummy table
---@param reason string
---@param pile_show boolean
VslUtil.BanishCards_Subcards = function(from, to, dummy, reason, pile_show)
  pile_show = pile_show or false
  local room = from.room
  local ids = {}
  local places = {}
  if to:getMark(reason.."_ids") ~= 0 then
    ids = to:getMark(reason.."_ids"):split("+")
  end
  if to:getMark(reason.."_places") ~= 0 then
    places = to:getMark(reason.."_places"):split("+")
  end
  local true_reason = VslUtil.Banish_Exchange(reason)
  for _,id in ipairs(dummy.subcards) do
    table.insert(ids, id)
    table.insert(places, room:getCardArea(id))
  end
  if #dummy.subcards > 0 then
    room:sendLog{
      type = "#v_banish_log",
      from = from.id,
      --log.to是数组
      to = {to.id},
      arg = true_reason,
      arg2 = #dummy.subcards,
    }
    to:addToPile(reason, dummy, pile_show, true_reason)
  end
  if reason ~= true_reason then
    room:setPlayerMark(to, reason.."_ids", table.concat(ids, "+"))
    room:setPlayerMark(to, reason.."_places", table.concat(places, "+"))
  end
end

--- 检查玩家是否存在对应阶段
---@param player ServerPlayer
---@param Phase integer
---@return boolean
VslUtil.exist_or_not = function(player, Phase)
  local exist_truth = true
  if player.skipped_phases ~= {} then
      for k in ipairs(player.skipped_phases) do
        if k == Phase then
            exist_truth = false
        end
      end
  end
  return exist_truth
end

--- 检查是否不能使用、打出或弃置某种牌
---@param player ServerPlayer
---@param card Card
---@return boolean
VslUtil.is_jilei = function(player, card)
  local is_jilei_or_not = false
  -- if not player:canUse(card) or player:prohibitUse(card) or player:prohibitDiscard(card) or player:prohibitResponse(card) then
  if player:prohibitUse(card) or player:prohibitDiscard(card) or player:prohibitResponse(card) then
    is_jilei_or_not = true
  end
  return is_jilei_or_not
end

--- 检查是否受伤（不涉及回复的话用这个）
---@param player ServerPlayer
---@return boolean
VslUtil.is_wounded = function(player)
  local chain_wounded = false
  for _,p in ipairs(Fk:currentRoom().alive_players) do
  -- for _,p in ipairs(room.alive_players) do
  -- for _,p in ipairs(Fk:currentRoom():getAlivePlayers()) do
    -- if p:getMark("v_wounded_chained") > 0 then
    if p:hasSkill("v_bingfu") then  
      chain_wounded = true
    end
  end
  if chain_wounded then
    return player.chained or player:isWounded()
  else
    return player:isWounded()
  end
end

--------------------------------------------------
--判断是否能被技能指定为目标
--注意与ai同步
--------------------------------------------------

VslUtil.SkillCanTarget = function(to, player, skill_name)
  -- if player ~= to and to:hasSkill("muying") then	--幕影
  --   return false
  -- end
  if skill_name == "v_lingan" and to:getMark("v_lingan_banned") > 0 then	--灵黯
    return false
  end
  -- if to:getMark("&suoke") > 0 then	--缩壳
  --   return false
  -- end
  -- if skill_name == "techong" and to:getMark("techong_cannot_target") > 0 then	--特铳
  --   return false
  -- end
  -- if to:getMark("banned_"..skill_name) > 0 or 
  --   to:getMark("banned") > 0  or 
  --   (to:getMark("banned_others") > 0 and player ~= to) then
  --   return false
  -- end
  return true
end

--回合弃牌区
---@param room Room
---@return integer[]
VslUtil.getCurrentCenterzone = function(room)
  local ids = {}
  if not room.current or room.current.dead then return {} end
  local logic = room.logic
  local events = logic.event_recorder[GameEvent.MoveCards] or Util.DummyTable
  if not logic:getCurrentEvent():findParent(GameEvent.Turn, true) then return {} end
  local end_id = logic:getCurrentEvent():findParent(GameEvent.Turn, true).id
  for i = #events, 1, -1 do
    local e = events[i]
    if e.id < end_id then break end
    for _, move in ipairs(e.data) do
      if move.toArea == Card.DiscardPile then
        for _, info in ipairs(move.moveInfo) do
          table.insertIfNeed(ids, info.cardId)
        end
      end
    end
  end
  local cards = table.simpleClone(ids)
  for _, id in ipairs(cards) do
    if room:getCardArea(id) ~= Card.DiscardPile then
      table.removeOne(ids, id)
    end
  end
  local ignores = room.current:getMark("ignore_centerzone-turn")
  if type(ignores) == "table" and #ignores > 0 then
    for _, id in ipairs(ignores) do
      if room:getCardArea(id) == Card.DiscardPile then
        table.removeOne(ids, id)
      end
    end
  end
  return ids
end

--移去
---@param room Room
---@param ids integer[] | nil
VslUtil.ignoreCenterzone = function(room, ids)
  local ignores = room.current:getMark("ignore_centerzone-turn")
  if type(ignores) ~= "table" then ignores = {} end
  if type(ids) == "table" and #ids > 0 then
    for _, id in ipairs(ids) do
      room:sendLog{
        type = "#ignoreCenterzoneCard",
        arg = Fk:getCardById(id, true):toLogString(),
      }
    end
    table.insertTable(ignores, ids)
  end
  room:setPlayerMark(room.current, "ignore_centerzone-turn", ignores)
end

--- 判断花色相近
---@param suit Suit
---@return Suit
VslUtil.suit_close = function(suit)
    if suit == Card.Spade then
        return Card.Club
    elseif suit == Card.Club then
        return Card.Spade
    elseif suit == Card.Heart then
        return Card.Diamond
    elseif suit == Card.Diamond then
        return Card.Heart
    end
    return Card.NoSuit
end
  
--- 判断花色相对
---@param suit Suit
---@return Suit
VslUtil.suit_opposite = function(suit)
    if suit == Card.Spade then
        return Card.Heart
    elseif suit == Card.Club then
        return Card.Diamond
    elseif suit == Card.Heart then
        return Card.Spade
    elseif suit == Card.Diamond then
        return Card.Club
    end
    return Card.NoSuit
end

--- 返回花色数字对应的字段
---@param suit Suit
---@return string
VslUtil.suit_string = function(suit)
    if suit == Card.Club then
        return "club"
    elseif suit == Card.Spade then
        return "spade"
    elseif suit == Card.Diamond then
        return "diamond"
    elseif suit == Card.Heart then
        return "heart"
    end
    return "unknown"
end

--- 返回花色数字对应的字段
---@param s string
---@return Suit
VslUtil.suit_number = function(s)
  if s == "club" then
    return Card.Club
  elseif s == "spade" then
    return Card.Spade
  elseif s == "diamond" then
    return Card.Diamond
  elseif s == "heart" then
    return Card.Heart
  end
end

---返回phase对应的字符串
---请注意8 notactive、9 phasenone暂无官方叫法，暂未写入
---@param phase_int integer
---@return string
VslUtil.phase_string = function(phase_int)
  if phase_int == 1 then
    return "回合开始"
  elseif phase_int == 2 then
    return "准备"
  elseif phase_int == 3 then
    return "判定"
  elseif phase_int == 4 then
    return "摸牌"
  elseif phase_int == 5 then
    return "出牌"
  elseif phase_int == 6 then
    return "弃牌"
  elseif phase_int == 7 then
    return "结束"
  elseif phase_int == 8 then
    return "回合外"
  elseif phase_int == 9 then
    return "空白"
  end
  return "未知"
end

---交互方式逃课方案1
---@param player ServerPlayer
---@param skill_name string
---@param prompt string|nil @ 提示信息
---@return boolean
VslUtil.yes_or_no = function(player, skill_name, prompt)
  local room = player.room
  local choiceList = {}
  table.insert(choiceList, "confirm")
  table.insert(choiceList, "cancel")
  local choice = room:askForChoice(player, choiceList, skill_name, prompt)
  if choice == "confirm" then
    return true
  end
  return false
end

--------------------------------------------------
--以下原型为官方utility包的内容
--可能会进行本土化改造（例如鬼勇）
--------------------------------------------------

-- 一名角色A对角色B使用牌的合法性检测
---@param room Room
---@param from ServerPlayer @ 使用来源
---@param to ServerPlayer @ 目标角色
---@param card Card @ 被使用的卡牌
---@param distance_limited boolean|nil @ 是否有距离关系的限制
---@param times_limited boolean|nil @ 是否有次数限制
VslUtil.canUseCardTo = function(room, from, to, card, distance_limited, times_limited)
  if from:prohibitUse(card) or from:isProhibited(to, card) then return false end
  if not times_limited then
    room:setPlayerMark(from, MarkEnum.BypassTimesLimit .. "-tmp", 1)
  end
  local can_use = from:canUse(card)
  if not times_limited then
    room:setPlayerMark(from, MarkEnum.BypassTimesLimit .. "-tmp", 0)
  end
  return can_use and card.skill:modTargetFilter(to.id, {}, from.id, card, distance_limited)
end

-- 获取使用牌的合法额外目标（【借刀杀人】等带副目标的卡牌除外）
---@param room Room
---@param data CardUseStruct @ 使用事件的data
---@param bypass_distances boolean|nil @ 是否无距离关系的限制
---@return integer[] @ 返回满足条件的player的id列表
VslUtil.getUseExtraTargets = function(room, data, bypass_distances)
  if not (data.card.type == Card.TypeBasic or data.card:isCommonTrick()) then return {} end
  if data.card.skill:getMinTargetNum() > 1 then return {} end --stupid collateral
  local tos = {}
  local current_targets = TargetGroup:getRealTargets(data.tos)
  for _, p in ipairs(room.alive_players) do
    if not table.contains(current_targets, p.id) and not room:getPlayerById(data.from):isProhibited(p, data.card) then
      if data.card.skill:modTargetFilter(p.id, {}, data.from, data.card, not bypass_distances) then
        table.insert(tos, p.id)
      end
    end
  end
  return tos
end

return VslUtil